package com.cndcare.msc.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.cndcare.msc.bean.ChannelDTO;
import com.cndcare.msc.components.ChannelRepository;
import com.cndcare.msc.config.MyWebSocket;
import com.cndcare.msc.netty.bean.BindAlarmBean;
import com.cndcare.msc.netty.bean.DangerLogBean;
import com.cndcare.msc.netty.bean.PcBean;
import com.cndcare.msc.netty.dao.jpa.BindAlarmBeanJpa;
import com.cndcare.msc.netty.dao.jpa.DangerLogBeanJpa;
import com.cndcare.msc.netty.dao.jpa.PcBeanJpa;
import com.cndcare.msc.protobuf.CmdEnum;
import com.cndcare.msc.protobuf.MessageVo;
import com.cndcare.msc.util.WxUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 实现心跳的hander  支持超时断开客户端避免浪费资源
 */
@Component(value = "serverHeartHandler")
@ChannelHandler.Sharable
@Slf4j
public class ServerHeartHandler extends ChannelInboundHandlerAdapter {
    // 设置6秒检测chanel是否接受过心跳数据
    private static final int READ_WAIT_SECONDS = 6;

    // 定义客户端没有收到服务端的pong消息的最大次数
    private static final int MAX_UN_REC_PING_TIMES = 5;

    @Autowired
    @Qualifier("channelRepository")
    private ChannelRepository channelRepository;

    @Autowired
    private PcBeanJpa pcBeanJpa;
    @Autowired
    private MyWebSocket myWebSocket;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private DangerLogBeanJpa dangerLogBeanJpa;
    @Autowired
    private WxUtils wxUtils;
    @Autowired
    private BindAlarmBeanJpa bindAlarmBeanJpa;

    /*空闲触发器 心跳基于空闲实现*/
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            String type = "";
            if (event.state() == IdleState.READER_IDLE) {
                type = "read idle";
                ChannelDTO channelDTO = channelRepository.get(ctx.channel().id().toString());

                String txt = stringRedisTemplate.opsForValue().get(channelDTO.getMac());
                Integer unRecPingTimes = MAX_UN_REC_PING_TIMES;
                if (StringUtils.isNotEmpty(txt)) {
                    unRecPingTimes = Integer.parseInt(txt);
                }

                if (unRecPingTimes >= MAX_UN_REC_PING_TIMES) {
                    // 连续超过N次未收到client的ping消息，那么关闭该通道，等待client重连
                    ctx.channel().close();
                    log.error(ctx.channel().id() + "超时类型：" + type);
                } else {
                    // 失败计数器加1
                    stringRedisTemplate.boundValueOps(channelDTO.getMac()).increment();
                }
            } else if (event.state() == IdleState.WRITER_IDLE) {
                type = "write idle";
            } else if (event.state() == IdleState.ALL_IDLE) {
                type = "all idle";
            }
            log.info("idle = {}", type);
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MessageVo messageVo = (MessageVo) msg;

        String mac = messageVo.getMac();
        ChannelDTO channelDTO = channelRepository.get(ctx.channel().id().toString());
        Channel channel = ctx.channel();
        if (null == channelDTO) {
            channelRepository.put(ctx.channel().id().toString(),
                    ChannelDTO.builder()
                            .mac(messageVo.getMac())
                            .channel(channel)
                            .build()
            );
        }
        /*认证处理*/
        if (messageVo.getCmdEnum().equals(CmdEnum.AUTH)) {
            log.info("收到客户端Id是" + mac + "的建立连接请求...");

            channelRepository.put(ctx.channel().id().toString(),
                    ChannelDTO.builder()
                            .mac(messageVo.getMac())
                            .ip(messageVo.getIp())
                            .channel(channel)
                            .build()
            );

            // 通过mac地址处理主机数据
            Date date = new Date();
            PcBean pcBean = pcBeanJpa.findByMac(messageVo.getMac());

            if (null != pcBean) {
                /**
                 * 主机登录
                 * 运算该台主机状态
                 * 若为警报状态，不允处理
                 */
                if (!pcBean.getStatus().equals(PcBean.DANGER)) {
                    pcBean.setRealIp(messageVo.getIp());
                    pcBean.setUpdateTime(date);

                    boolean infoFlag = true;
                    if (StringUtils.isNotEmpty(pcBean.getRealIp()) && !pcBean.getRealIp().equals(pcBean.getIp())) {
                        pcBean.setInfoStatus(PcBean.INFO_CONFLICT);
                        pcBean.setStatus(PcBean.WARN);
                        infoFlag = false;
                    }

                    if (StringUtils.isEmpty(pcBean.getAddress())
                            || StringUtils.isEmpty(pcBean.getRemark())) {
                        pcBean.setInfoStatus(PcBean.UN_COMPLETE);
                        pcBean.setStatus(PcBean.WARN);
                        infoFlag = false;
                    }
                    if (infoFlag) {
                        /**
                         * 数据正常
                         * 若为离线状态，修改为在线状态
                         */
                        pcBean.setInfoStatus(PcBean.COMPLETE);
                        if (pcBean.getStatus().equals(PcBean.OFFLINE)) {
                            pcBean.setStatus(PcBean.ONLINE);
                        }
                    }

                    pcBean.setLevel(PcBean.levelMap.get(pcBean.getStatus()));
                    pcBeanJpa.saveAndFlush(pcBean);
                }
            } else {
                // 新注解监控主机
                pcBeanJpa.save(PcBean.builder()
                        .mac(messageVo.getMac())
                        .ip(messageVo.getIp())
                        .status(PcBean.WARN)
                        .level(PcBean.levelMap.get(PcBean.WARN))
                        .realIp(messageVo.getIp())
                        .createTime(date).build());
            }

            // 状态变更
            myWebSocket.broadcastStatus();

            /**
             * redis写入在线心跳数据
             * 此为全局主机检索状态依据
             */
            stringRedisTemplate.opsForValue().set(
                    messageVo.getMac(), "0", 60, TimeUnit.SECONDS
            );

            String data = "客户端Id是" + mac + "的建立连接请求success！";
            ctx.writeAndFlush(
                    MessageVo.builder()
                            .mac(messageVo.getMac())
                            .ip(messageVo.getIp())
                            .cmdEnum(CmdEnum.AUTH_BACK)
                            .data(data).build());
        } else if (messageVo.getCmdEnum().equals(CmdEnum.PING)) {
            // 处理ping消息
            String data = "服务器响应客户端Id是" + mac + "的心跳包";
            log.info("msg = {}", data);
            ctx.writeAndFlush(
                    MessageVo.builder()
                            .mac(messageVo.getMac())
                            .ip(messageVo.getIp())
                            .cmdEnum(CmdEnum.PONG)
                            .data(data).build());
            // redis覆盖在线数据
            stringRedisTemplate.opsForValue().set(
                    messageVo.getMac(), "0", 60, TimeUnit.SECONDS
            );
        } else if (messageVo.getCmdEnum().equals(CmdEnum.REFRESH_ALL)) {
            log.info("收到刷新指令 = {}", messageVo);

            myWebSocket.broadcast(messageVo);
        } else if (messageVo.getCmdEnum().equals(CmdEnum.DANGER)) {
            log.info("收到告警数据 = {}", messageVo);
            Date date = new Date();
            PcBean pcBean = pcBeanJpa.findByMac(messageVo.getMac());
            long between = DateUtil.between(pcBean.getUpdateTime(), date, DateUnit.SECOND);
            if (null == pcBean.getUpdateTime() || between > 5) {
                pcBean.setStatus(PcBean.DANGER);
                pcBean.setUpdateTime(new Date());
                pcBean.setLevel(PcBean.levelMap.get(PcBean.DANGER));
                pcBeanJpa.saveAndFlush(pcBean);

                // 状态变更
                myWebSocket.broadcastStatus();

                new Thread(() -> {
                    DangerLogBean dangerLogBean = new DangerLogBean();
                    BeanUtil.copyProperties(pcBean, dangerLogBean, "id", "createTime", "updateTime");
                    dangerLogBean.setCreateTime(new Date());
                    dangerLogBean.setStatus(DangerLogBean.WAIT_DEAL);
                    dangerLogBeanJpa.save(dangerLogBean);

                    List<BindAlarmBean> all = bindAlarmBeanJpa.findAll();

                    for (BindAlarmBean bindAlarmBean : all) {
                        wxUtils.医院通知(
                                bindAlarmBean.getOpenId(),
                                "医生端告警通知",
                                DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"),
                                "告警地址：" + pcBean.getAddress(),
                                "请速去查看",
                                ""
                        );
                    }
                }).start();
            }
        } else {
            if (channel.isOpen()) {
                //触发下一个handler
                ctx.fireChannelRead(msg);
            }
        }
        ReferenceCountUtil.release(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ChannelDTO channelDTO = channelRepository.get(ctx.channel().id().toString());

        PcBean pcBean = pcBeanJpa.findByMac(channelDTO.getMac());
        if (!pcBean.getStatus().equals(PcBean.DANGER)) {
            pcBean.setStatus(PcBean.OFFLINE);
            pcBean.setLevel(PcBean.levelMap.get(PcBean.OFFLINE));
            pcBeanJpa.saveAndFlush(pcBean);
        }

        // 删除redis在线数据
        stringRedisTemplate.delete(pcBean.getMac());

        // 状态变更
        myWebSocket.broadcastStatus();

        log.error("客户端断开链接，Mac是 " + channelDTO.getMac() + "----错误详情是======" + cause.getMessage());
    }
}
